﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TechnicalIndicators.DateBased;
using TechnicalIndicators.Points;
using Utils;

namespace TechnicalIndicators.DateBaseBased
{
	public class RSI : GeneratorBase<DatedSeriePoint, DatedSeriePoint>
	{
		#region Attributes

		private int _period;
		private RSIMode _mode;

		private GeneratorBase<DatedSeriePoint, DatedSeriePoint> _upAverage;
		private GeneratorBase<DatedSeriePoint, DatedSeriePoint> _downAverage;

		private DatedSeriePoint _previousPoint;

		private ReadWriteLockedObservableCollection<DatedSeriePoint> _upValues;
		private ReadWriteLockedObservableCollection<DatedSeriePoint> _downValues;

		#endregion

		public RSI() : base()
        {
			_upValues = new ReadWriteLockedObservableCollection<DatedSeriePoint>(TimeSpan.FromSeconds(10));
			_downValues = new ReadWriteLockedObservableCollection<DatedSeriePoint>(TimeSpan.FromSeconds(10));
        }

		public RSI(ObservableCollection2<DatedSeriePoint> destination) : base(destination)
        {
			_upValues = new ReadWriteLockedObservableCollection<DatedSeriePoint>(TimeSpan.FromSeconds(10));
			_downValues = new ReadWriteLockedObservableCollection<DatedSeriePoint>(TimeSpan.FromSeconds(10));
        }

		public void Configure(int period, RSIMode mode)
		{
			switch (mode)
			{
				case RSIMode.Standard:
					_upAverage = new RSIAverage();
					_downAverage = new RSIAverage();

					((RSIAverage)_upAverage).Configure(period);
					((RSIAverage)_downAverage).Configure(period);
					break;

				case RSIMode.SMA:
					_upAverage = new MovingAverage();
					_downAverage = new MovingAverage();

					((MovingAverage)_upAverage).Configure(period);
					((MovingAverage)_downAverage).Configure(period);
					break;

				case RSIMode.EMA:
					_upAverage = new ExponentialAverage();
					_downAverage = new ExponentialAverage();

					((ExponentialAverage)_upAverage).Configure(period);
					((ExponentialAverage)_downAverage).Configure(period);
					break;
			}

			_upAverage.Source = _upValues;
			_downAverage.Source = _downValues;

			_period = period;
			_mode = mode;
		}

		public override void Start()
		{
			_upAverage.Start();
			_downAverage.Start();

			base.Start();
		}

		public override void Clear()
		{
			_upAverage.Clear();
			_downAverage.Clear();

			_upValues.GetWriteLock().Clear();
			_downValues.GetWriteLock().Clear();
			_upValues.FreeWriteLock();
			_downValues.FreeWriteLock();

			_previousPoint = null;

			base.Clear();
		}

		protected override DatedSeriePoint CreatePoint(DatedSeriePoint sourcePoint)
		{
			DatedSeriePoint result = null;

			if (_previousPoint != null)
			{
				this.FeedSources(sourcePoint);
			}

			if (_upAverage.LastValue != null && _downAverage.LastValue != null)
			{
				decimal rs = _downAverage.LastValue.Y == 0 ? 100 : _upAverage.LastValue.Y / _downAverage.LastValue.Y;
				decimal rsi = 100 - (100 / (1 + rs));

				result = new DatedSeriePoint(sourcePoint.X, rsi);
			}
			_previousPoint = sourcePoint;

			return result;
		}

		#region Private Methods

		private void FeedSources(DatedSeriePoint sourcePoint)
		{
			DatedSeriePoint upPoint;
			DatedSeriePoint downPoint;

			if (sourcePoint.Y > _previousPoint.Y)
			{
				upPoint = new DatedSeriePoint(sourcePoint.X, sourcePoint.Y - _previousPoint.Y);
				downPoint = new DatedSeriePoint(sourcePoint.X, 0);
			}
			else if (sourcePoint.Y < _previousPoint.Y)
			{
				upPoint = new DatedSeriePoint(sourcePoint.X, 0);
				downPoint = new DatedSeriePoint(sourcePoint.X, _previousPoint.Y - sourcePoint.Y);
			}
			else
			{
				upPoint = new DatedSeriePoint(sourcePoint.X, 0);
				downPoint = new DatedSeriePoint(sourcePoint.X, 0);
			}

			_upValues.GetWriteLock().Add(upPoint);
			_upValues.FreeWriteLock();
			_downValues.GetWriteLock().Add(downPoint);
			_downValues.FreeWriteLock();
		}

		#endregion
	}
}
